#!/usr/bin/env bash

#git alias autocomplete
if [[ $(declare -f __git_complete) ]]; then
  while read line
  do
    __git_complete ${line%%:*} ${line##*:}
  done < <(cat <<EOF
gbr:_git_branch
glr:_git_checkout
gco:_git_checkout
gps:_git_checkout
gcp:_git_checkout
grs:_git_checkout
gme:_git_checkout
grb:_git_checkout
gll:_git_checkout
gl:_git_checkout
gdf:_git_diff
gsh:_git_stash
gdfc:_git_diff
gsm:_git_submodule
EOF
)
fi

function gcom() {
  local _files _grep _prompt _patch
  if [[ $* =~ $(echo '(\s+-p|-p\b)') ]]; then
    _patch='-p'
    set -- $(echo $* | sed -E 's/(^|\s)-p\b//')
  fi
  _grep="$1"
  [[ $_grep ]] || {
    _prompt='grep a file name: '
    echo 'checkout multi files from HEAD'
    echo -n "$_prompt"
    while read -p '' _grep
    do
      [[ $_grep ]] && break || { echo -n "$_prompt"; continue; }
    done
  }
  if [[ $_grep ]]; then
    # grep file of the HEAD to checkout
    _files=$(multi_select "$(git diff HEAD --name-only | grep "$_grep")")
    if [[ "$_files" ]]; then
      gco ${_patch-} $_files
    fi
  fi
}
function gdff() {
  git_last $@
  gdf $_rest $_last
  git_last_unset
}
function gshu() {
  git stash && git stash save -u "$1" && git stash pop stash@{1}
}
function gss() {
  if [[ $# -gt 1 ]] && [[ ${@: -1} =~ ^- ]]; then
    _p="${2:+-p}"
    _stash="$1"
  elif [[ $# -eq 1 ]]; then
    if [[ $1 =~ ^- ]]; then
      _p="${1:+-p}"
      _stash=0
    elif [[ $1 =~ [0-9]+ ]]; then
      _stash="$1"
    fi
  else
    _stash="0"
  fi
  git stash show stash@{$_stash} ${_p}
}
function gsf() {
  local _stat_p= _grep _path _commitid _option
  if [[ $# -ge 2 ]]; then
    _grep="$1"
    if [[ $2 =~ --stat|-p ]]; then
      _stat_p="$2"
    else
      _path="$2"
    fi
    shift 2
  elif [[ $# -eq 1 ]]; then
    _grep="$1"
    shift
  elif [[ $# -eq 0 ]]; then
    _commitid='HEAD'
    _stat_p='--stat'
  fi
  if [[ -z $_commitid ]]; then
    if [[ $(git cat-file -t $_grep) = 'commit' ]]; then
      _commitid=$_grep
    else
      _commitid=$(get_commitid_by_msg "$(git log --oneline | grep "$_grep")")
    fi
  fi
  if [[ -n $_path ]]; then
    git show ${_commitid}:${_path} $*
  elif [[ -n $_stat_p ]]; then
    git show ${_commitid} ${_stat_p} $*
  else
    _option=$(get_select_option "$(git diff-tree --no-commit-id --name-only -r $_commitid)")
    git show ${_commitid}:${_option} $*
  fi
}
function gsw() {
  # exists in current dir
  local file
  if [[ $# -eq 0 ]]; then
    git ls-files -v | grep ^S
    return
  fi
  if [[ -e ${@: -1} ]]; then
    file=${@: -1}
  else
    file="*${@: -1}*"
  fi
  git update-index --skip-worktree "${@: 1:$(($#-1))}" $file
  glsw
}
function gnsw() {
  # exists in current dir
  local file
  if [[ $# -eq 0 ]]; then
    git ls-files -v | grep ^S
    return
  fi
  if [[ -e ${@: -1} ]]; then
    file=${@: -1}
  else
    file="*${@: -1}*"
  fi
  git update-index --no-skip-worktree "${@: 1:$(($#-1))}" $file
  glsw
}
function glsw() {
  git ls-files -v | grep -iP '^S' | grep -iP "${1-}"
}
function gvm() {
  local _files _regex _ofiles
  _files="$(echo "$(git ls-files -m && git diff --cached --name-only --diff-filter=AM)" | sort | uniq)"
  if [[ -n $_files ]]; then
    if [[ $# -gt 1 ]]; then
      _regex="$(echo $* | sed 's/ /|/g')"
      _ofiles="$(echo "$_files" | grep -P "$_regex")"
    elif [[ $# -gt 0 ]]; then
      _ofiles="$(echo "$_files" | grep "$1")"
    else
      _ofiles="$(git ls-files -m && git diff --cached --name-only --diff-filter=AM)"
    fi
    if [[ -n $_ofiles ]]; then
      vu -p $_ofiles
    fi
  fi
}
function gsmu() {
  gsm update ${*---init --recursive}
}
function git_last() {
  unset _last _rest
  local _last_is_commit
  if [[ $# -gt 1 ]] && ! [[ ${@: -1} =~ ^- ]]; then
    _last_is_commit="${@: -1}"
    _rest="${@: 1:$(($#-1))}"
  elif [[ $# -gt 0 ]] && ! [[ ${@: -1} =~ ^- ]]; then
    _last_is_commit="${@: -1}"
  elif [[ ${@: -1} =~ ^- ]]; then
    _last="."
    _rest="$@"
  else
    _last="."
  fi
  [[ "$_last_is_commit" ]] && _last="$([[ $(git cat-file -t $_last_is_commit 2>/dev/null) = 'commit' ]] && echo "$_last_is_commit" || echo "*$_last_is_commit*")"
  _rest=${_rest-}
}
function git_last_unset() {
  unset _last _rest
}
function gad() {
  git_last $@
  git add $_rest "${_last}"
  gst
  git_last_unset
}
function gadc() {
  gad $*
  local _files="$(git diff --cached --name-only)"
  if [[ "$_files" ]]; then
    pcf $_files &>/dev/null
    gad $_files &>/dev/null
  fi
}
function grh() {
  git_last $@
  git reset HEAD $_rest $_last
  git_last_unset
  gst
}
function gsbcf() {
  local prefix remote branch
  prefix=$1
  remote=$2
  branch=$3
  git ls-remote --exit-code ${remote} &>/dev/null
  if test $? != 0
  then
    echo 'remote not exists'
  fi
  git config remote.${remote}.prefix ${prefix}
  git config remote.${remote}.branch ${branch}
  git config --get-regexp remote.${remote}
}
function gdfcf() {
  gdf --cached "*${1}*"
}

function gsbps() {
  pull_push='push'
  gsbpl $1
}

function gsbpl() {
  local prefix remote branch pull_push gdr squash_and_msg
  pull_push=${pull_push:-pull}
  remote=$1

  # check if remote exists
  git ls-remote --exit-code ${remote} &>/dev/null
  if test $? = 0
  then
    cd $(git rev-parse --show-toplevel)
  fi
  prefix=$(git config --get "remote.${remote}.prefix")
  if [ -z "${prefix}" ] ; then echo 'subtree prefix not found';return; fi
  branch=$(git config --get "remote.${remote}.branch")
  if [ -z "${branch}" ] ; then echo 'subtree branch not found';return; fi
  if test "${pull_push}" = "pull"; then
    squash_and_msg="--squash -m "
    if test -z "${@:2}"; then
      squash_and_msg="${squash_and_msg} merge"
    fi
  fi
  git subtree "${pull_push}" --prefix="${prefix}" "${remote}" "${branch}" ${squash_and_msg:-} "${@:2}"
  if test "$?" = 0 ; then cd -; &>/dev/null; fi
}
function gfet() {
  git fetch --no-tags $1 +refs/tags/$2:refs/tags/$2
}
function glp() {
  local _stat
  [[ $* =~ '--stat' ]] && { _stat='--stat'; }
  git_last $@
  git log --oneline ${_stat:--p} ${_rest:=-1} $_last
  git_last_unset
}
function glr() {
  local all
  all="$*"
  all="${all/\ ...\ /...}"
  all="${all/\ ..\ /..}"
  git log --oneline --left-right $all
}
function gld() {
  local diff_branch current_branch upstream
  diff_branch="$1"
  if [[ ! -n "$1" ]]; then
    current_branch=$(git rev-parse --abbrev-ref HEAD);
    upstream=$(git rev-parse --abbrev-ref ${current_branch}@{upstream});
    diff_branch="${current_branch}...${upstream}"
  fi
  git log --left-right --graph --oneline "$diff_branch"
}
function gpl() {
  local current_branch=$(git rev-parse --abbrev-ref HEAD);
  local remote=$(git config --get-regexp "branch\.$current_branch\.remote" | sed -e "s/^.* //")
  if [[ -n $current_branch ]] && [[ -n $remote ]] && [[ -z $* ]]; then
  	local remote_branch=$(git config --get-regexp "branch\.$current_branch\.mrege" | sed -e "s@^.*/refs/heads/@@")
    git pull $remote $remote_branch
  else
    git pull $*
  fi
}
function gps() {
  local remote remote_branch current_branch upstream
  current_branch=$1
  if [[ -n $current_branch ]]; then
  	upstream=$(git branch -lvv | grep -P '^\s*\*?\s*'$current_branch' ' | cut -sd'[' -f2 | cut -d']' -f1| cut -d':' -f1);
  fi
  if [[ $upstream ]]; then
    shift
    remote=${upstream%%/*}
    branch=${upstream//*\/}
    git push $remote $current_branch:$branch $*
  else
    git push $*
  fi
}
function gsa() {
  local _stash=${1-0}
  git stash apply stash@{$_stash}
}
function gsp() {
  local _stash=${1-0}
  git stash pop stash@{$_stash}
}
function gsd() {
  local _stash=${1-0}
  git stash drop stash@{$_stash}
}
function gcimp() {
  local msg
  msg=${1:-+++}
  git commit -m "$msg" && git push ${@:2}
}
function gcim() {
  local msg
  msg="${@: -1}"
  git commit "${@: 1:$(($#-1))}" -m "${msg:-+++}"
}
function gclef() {
  local _files
  _files="$(glso)"
  if [[ "$_files" ]]; then
    \rm $(multi_select "$(echo "$_files" | grep $1)")
  fi
  git status
}
function gcls() {
  local _branch_name=$1 _repo=$2
  git clone --single-branch -b $_branch_name $_repo
}
function get_commitid_by_msg() {
  local _commitid _line OLD_IFS commit
  _line=$(echo "$*" | sed '/^$/d' | wc -l)
  if [[ $_line -eq 1 ]]; then
    echo "$*" | cut -d' ' -f1
  elif [[ $_line -gt 1 ]]; then
    OLD_IFS=${IFS}
    OLD_PS3=${PS3}
    IFS=$'\n'
    PS3="choose a commit: "
    select commit in $(echo "$*"); do
      if [[ -n $commit ]]; then
        echo $commit | cut -d' ' -f1
        break
      else
        echo 'out of range'
      fi
    done
    IFS=${OLD_IFS}
    PS3=${OLD_PS3}
  else
    echo 'commit message grep-results is none'
    return 1
  fi
}
function gcif() {
  local _commit
  if [[ "$1" ]]; then
    if [[ $(git cat-file -t $1) = 'commit' ]]; then
      _commit=$1
    else
      _commit=$(get_commitid_by_msg "$(git log --oneline --grep "$1" | grep -v 'fixup!')")
    fi
  fi
  git commit --fixup ${_commit-HEAD}
}
function grbs() {
  local _commitid _previd
  # git rebase --autosquash after greped commit
  if [[ -z $1 ]]; then
    echo 'grep a commit message to autosquash'
    return 1
  fi
  _commitid=$(get_commitid_by_msg "$(git log --oneline --grep "$1" | grep -v 'fixup!')")
  if [[ -n $_commitid ]]; then
    _previd=$(git rev-parse $_commitid^ | cut -c -7)
    if [[ -n $_previd ]]; then
      git rebase -i --autosquash "$_previd"
    fi
  fi
}
function gms() {
  git merge --squash $1
  git commit
}
function grtgitee() {
  local repo_name orig_url
  orig_url=$(git config --get remote.origin.url)
  repo_name=${orig_url##*/}
  git remote set-url origin git@gitee.com:stiny/${repo_name}
}
gfb() {
  git filter-branch --index-filter "git rm --cached --ignore-unmatch $1" $2
  rm -rf .git/refs/original/
  git reflog expire --expire=now --all
  git fsck --full --unreachable
  git repack -A -d
  git gc --aggressive --prune=now
}
